/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *    Copyright 2021 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
 */

#include "eventloop_posix.h"

#if !defined(UA_HAVE_EPOLL)

/**
 * \brief 事件循环注册文件描述
 */
UA_StatusCode UA_EventLoopPOSIX_registerFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) {
    UA_LOCK_ASSERT(&el->elMutex, 1);
    UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP,
                 "Registering fd: %u", (unsigned)rfd->fd);

    /* 重新分配文件描述 */
    UA_RegisteredFD **fds_tmp = (UA_RegisteredFD **)
        UA_realloc(el->fds, sizeof(UA_RegisteredFD *) * (el->fdsSize + 1));
    if (!fds_tmp) {
        return UA_STATUSCODE_BADOUTOFMEMORY;
    }
    el->fds = fds_tmp;

    /* 添加到新增的条目 */
    el->fds[el->fdsSize] = rfd;
    el->fdsSize++;
    return UA_STATUSCODE_GOOD;
}

UA_StatusCode
UA_EventLoopPOSIX_modifyFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) {
    /* Do nothing, it is enough if the data was changed in the rfd */
    UA_LOCK_ASSERT(&el->elMutex, 1);
    return UA_STATUSCODE_GOOD;
}

void
UA_EventLoopPOSIX_deregisterFD(UA_EventLoopPOSIX *el, UA_RegisteredFD *rfd) {
    UA_LOCK_ASSERT(&el->elMutex, 1);
    UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP,
                 "Unregistering fd: %u", (unsigned)rfd->fd);

    /* Find the entry */
    size_t i = 0;
    for(; i < el->fdsSize; i++) {
        if(el->fds[i] == rfd)
            break;
    }

    /* Not found? */
    if(i == el->fdsSize)
        return;

    if(el->fdsSize > 1) {
        /* Move the last entry in the ith slot and realloc. */
        el->fdsSize--;
        el->fds[i] = el->fds[el->fdsSize];
        UA_RegisteredFD **fds_tmp = (UA_RegisteredFD**)
            UA_realloc(el->fds, sizeof(UA_RegisteredFD*) * el->fdsSize);
        /* if realloc fails the fds are still in a correct state with
         * possibly lost memory, so failing silently here is ok */
        if(fds_tmp)
            el->fds = fds_tmp;
    } else {
        /* Remove the last entry */
        UA_free(el->fds);
        el->fds = NULL;
        el->fdsSize = 0;
    }
}

/**
 * \brief 设置文件描述符集合
 */
static UA_FD setFDSets(UA_EventLoopPOSIX *el, fd_set *readset, fd_set *writeset, fd_set *errset) {
    UA_LOCK_ASSERT(&el->elMutex, 1);

    /* 清空文件描述符集合 */
    FD_ZERO(readset);
    FD_ZERO(writeset);
    FD_ZERO(errset);
    UA_FD highestfd = UA_INVALID_FD;

    /* 遍历所有注册的文件描述符 */
    for(size_t i = 0; i < el->fdsSize; i++) {
        UA_FD currentFD = el->fds[i]->fd;

        /* 如果该 FD 注册了 UA_FDEVENT_IN（如套接字有数据可读），则将其加入 readset */
        if (el->fds[i]->listenEvents & UA_FDEVENT_IN) {
            FD_SET(currentFD, readset);
        }
        /* 如果注册了 UA_FDEVENT_OUT（如套接字可以发送数据），则加入 writeset */
        if (el->fds[i]->listenEvents & UA_FDEVENT_OUT) {
            FD_SET(currentFD, writeset);
        }

        /* 始终监听错误事件 */
        FD_SET(currentFD, errset);

        /*  跟踪最大文件描述符*/
        if (currentFD > highestfd || highestfd == UA_INVALID_FD) {
            highestfd = currentFD;
        }
    }
    return highestfd;
}

/**
 * \brief 事件循环文件描述符轮询
 *
 * @param el            相关的事件循环结构体
 * @param listenTimeout 监听超时时间
 *
 * @return 成功返回 UA_STATUSCODE_GOOD
 */
UA_StatusCode UA_EventLoopPOSIX_pollFDs(UA_EventLoopPOSIX *el, UA_DateTime listenTimeout) {
    UA_assert(listenTimeout >= 0);
    UA_LOCK_ASSERT(&el->elMutex, 1);

    fd_set readset, writeset, errset;
    /* 设置文件描述符集合，并返回最大的文件描述符 */
    UA_FD highestfd = setFDSets(el, &readset, &writeset, &errset);

    /* 文件描述符无效 */
    if (highestfd == UA_INVALID_FD) {
        UA_LOG_TRACE(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP,
                     "No valid FDs for processing");
        return UA_STATUSCODE_GOOD;
    }

    struct timeval tmptv = {
#ifndef UA_ARCHITECTURE_WIN32
        (time_t)(listenTimeout / UA_DATETIME_SEC),
        (suseconds_t)((listenTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC)
#else
        (long)(listenTimeout / UA_DATETIME_SEC),
        (long)((listenTimeout % UA_DATETIME_SEC) / UA_DATETIME_USEC)
#endif
    };

    UA_UNLOCK(&el->elMutex);
    int selectStatus = UA_select(highestfd+1, &readset, &writeset, &errset, &tmptv);
    UA_LOCK(&el->elMutex);
    if (selectStatus < 0) {
        /* We will retry, only log the error */
        UA_LOG_SOCKET_ERRNO_WRAP(
            UA_LOG_WARNING(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP,
                           "Error during select: %s", errno_str));
        return UA_STATUSCODE_GOOD;
    }

    /* 遍历所有已注册的文件描述符，以检查是否有事件到达
     * 这正是 select 在处理大量打开的套接字时速度慢的原因 */
    for (size_t i = 0; i < el->fdsSize; i++) {
        UA_RegisteredFD *rfd = el->fds[i];

        /* 该 rfd 已经被注册为待移除状态，不要再处理其传入的事件 */
        if (rfd->dc.callback) {
            continue;
        }

        /* 该文件描述符是否有事件触发*/
        short event = 0;
        if (FD_ISSET(rfd->fd, &readset)) {
            event = UA_FDEVENT_IN;
        } else if(FD_ISSET(rfd->fd, &writeset)) {
            event = UA_FDEVENT_OUT;
        } else if(FD_ISSET(rfd->fd, &errset)) {
            event = UA_FDEVENT_ERR;
        } else {
            continue;
        }

        UA_LOG_DEBUG(el->eventLoop.logger, UA_LOGCATEGORY_EVENTLOOP,
                     "Processing event %u on fd %u", (unsigned)event,
                     (unsigned)rfd->fd);

        /* 调用事件源回调函数 */
        rfd->eventSourceCB(rfd->es, rfd, event);

        /* 该文件描述符已将自身移除 */
        if (i == el->fdsSize || rfd != el->fds[i]) {
            i--;
        }
    }
    return UA_STATUSCODE_GOOD;
}

#endif /* !defined(UA_HAVE_EPOLL) */
